import { option, unifyOption, filesItem, files, watchOption } from './type'

import { AES } from './aes'

export default class Store {
  store: Storage
  files: files = {}
  unifyOption: unifyOption
  aes: AES
  constructor(store: Storage, unifyOption: unifyOption) {
    this.store = store
    this.unifyOption = unifyOption
    this.aes = new AES(unifyOption.secretKey)
    this.getFiles()
  }
  set(key: string, value: unknown, tempOption?: option) {
    // key需要有意义

    key = this.keyFormat(key)
    let type: string
    let watchs: Function[] = []
    type = typeof value
    if (this.isExistInFiles(key)) {
      watchs = this.files[key].watchs
      this.runWatch(key, {
        type: 'set',
        value,
      })
    }
    value = JSON.stringify(value)
    let option: option = {
      failureTime: tempOption?.failureTime || this.unifyOption.failureTime,
      encryption: tempOption?.encryption || this.unifyOption.encryption,
    }
    const storeObj: filesItem = {
      key,
      value,
      type,
      watchs,
      option,
    }
    if (option.failureTime > 0) {
      storeObj.timestamp = new Date().getTime()
    }
    if (option.encryption) {
      storeObj.value = this.aes.encrypt(value as string)
    }
    this.files[key] = storeObj
    this.store.setItem(key, JSON.stringify(storeObj))
    this.setFiles()
  }
  get(key: string) {
    key = this.keyFormat(key)
    const temp = this.store.getItem(key)
    if (!temp) return ''
    let response = {
      success: true,
      message: '',
      value: '',
    }
    let storeObj: any
    try {
      storeObj = JSON.parse(temp)
    } catch (e) {
      return {
        success: false,
        message: e,
        value: temp,
      }
    }
    if (
      storeObj?.timestamp &&
      storeObj.timestamp > 0 &&
      new Date().getTime() - storeObj.timestamp > storeObj.option.failureTime
    ) {
      return undefined
    }
    if (storeObj.option.encryption) {
      storeObj.value = this.aes.decrypt(storeObj.value as string)
    }
    try {
      response.value = JSON.parse(storeObj.value as string)
    } catch (e) {
      return {
        success: false,
        message: e,
        value: storeObj.value,
      }
    }
    this.runWatch(key, {
      type: 'get',
      value: response.value,
    })
    return response.value
  }
  // 删除单个键值
  remove(key: string) {
    this.store.removeItem(key)
    delete this.files[key]
  }
  // 清空
  clear() {
    this.store.clear()
    this.files = {}
  }
  // 开始一个监听
  on(key: string, callback: Function) {
    key = this.keyFormat(key)
    this.files[key].watchs.push(callback)
  }
  // 关闭一个监听
  off(key: string, callback: Function) {
    key = this.keyFormat(key)
    for (let i = 0; i < this.files[key].watchs.length; i++) {
      const element = this.files[key].watchs[i]
      if (element === callback) {
        this.files[key].watchs.slice(i, 1)
        break
      }
    }
  }
  // 执行watch中的监听
  private runWatch(key: string, option: watchOption) {
    for (let i = 0; i < this.files[key].watchs.length; i++) {
      this.files[key].watchs[i](option)
    }
  }
  // 判断记录的key是否存在
  private isExistInFiles(key: string): boolean {
    return !!this.files[key]
  }
  // 保存内容
  private setFiles() {
    this.store.setItem('cache-tools-store-manage', JSON.stringify(this.files))
  }
  // 读取内容
  private getFiles() {
    const cacheToolsStore = this.store.getItem('cache-tools-store-manage')
    if (cacheToolsStore) {
      const files: files = JSON.parse(cacheToolsStore)
      Object.keys(files).forEach((item) => {
        files[item].watchs = []
      })
    }
  }
  // 判断键名是否正确
  private keyFormat(key: unknown) {
    if (typeof key !== 'string' || !key) {
      throw new Error('键名应当为字符串')
    }
    return this.unifyOption.namespace + key
  }
}
